package ctrl

import (
	"fmt"
	"io/ioutil"
	"net"
	"strings"
	"time"

	"gitee.com/bjf-fhe/apinx/config"
	"gitee.com/bjf-fhe/apinx/errors"
	"gitee.com/bjf-fhe/apinx/model"
	"gitee.com/bjf-fhe/apinx/openapi"
	"github.com/extrame/goblet"
	"github.com/getkin/kin-openapi/openapi3"
	"github.com/sirupsen/logrus"
)

var gatewaySecret = []byte("rongapi.cn-jwt-fasdfefasdff")

type Gateway struct {
	goblet.Route `/`
	goblet.GroupController
	Def         *openapi3.T
	Targets     config.Servers
	QpsManager  *model.QpsManager
	Crypt       *config.Crypt
	ApiCfg      *config.OpenAPI
	cfg         *config.SourceGwConfig
	transformer *model.Transformer
}

func (g *Gateway) Init(server *goblet.Server) error {
	g.cfg = &config.SourceGwConfig{
		SmartEncrypt: g.ApiCfg.Encrypt,
	}
	g.transformer = model.NewTransformer(true)
	var fpe = &model.FpeEncrypter{
		Key:   g.Crypt.Key,
		Tweak: g.Crypt.Tweak,
	}
	var encrypter = &model.Encrypter{
		Type:      model.Encrpt_SubType_Replace_FPE,
		Encrypter: fpe,
		Decrypter: fpe,
	}
	g.transformer.Encrypter.Init([]*model.EncryptRule{
		{
			Name:      "身份证号码",
			Matcher:   `^([1-6][1-9]|50)\d{4}(18|19|20)\d{2}((0[1-9])|10|11|12)(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$`,
			Encrypter: encrypter,
		},
		{
			Name:      "手机号码",
			Matcher:   `^1[3|4|5|6|7|8|9][0-9]{9}$`,
			Encrypter: encrypter,
		},
	})
	return nil
}

func (g *Gateway) Get(ctx *goblet.Context) error {
	return g.handle(ctx)
}

func (g *Gateway) Post(ctx *goblet.Context) error {
	return g.handle(ctx)
}

type callingConfig struct {
	ServerId int
}

func (c *callingConfig) GetServerId() int {
	return c.ServerId
}

func (c *callingConfig) GetPathArgs(nameOfPath string) (*openapi.PathSelection, bool) {
	return nil, false
}

func (g *Gateway) handle(ctx *goblet.Context) (err error) {

	path := "/" + ctx.Suffix()

	//TODO read cfg from cache
	var host = ctx.ReqHeader().Get("origin")
	for _, v := range g.cfg.Cors {
		if host == v {
			ctx.SetHeader("Access-Control-Allow-Origin", v)
			break
		} else if v == "*" {
			ctx.SetHeader("Access-Control-Allow-Origin", host)
			break
		} else {
			logrus.Info("收到Cors，origin：", host, "in db:", v, "not right")
		}
	}

	//进行IP限制判断
	var rules []model.SourceIPRange
	var clientAddr = ctx.RemoteAddr()
	ip := net.ParseIP(clientAddr.String())
	for _, v := range rules {
		if v.CIDR.Contains(ip) {
			if v.Denied {
				err = errors.New(errors.ERROR_FORBIDDEN, "禁止该IP("+clientAddr.String()+")访问(1)")
				goto responseError
			} else {
				goto timeJudge
			}
		}
	}
	if g.cfg.NotAllowUnknownIp {
		err = errors.New(errors.ERROR_FORBIDDEN, "禁止该IP("+clientAddr.String()+")访问(2)")
		goto responseError
	}

	//进行时间限制判断
timeJudge:
	if g.cfg.TimeRanges != nil {
		var now = time.Now()
		var timeRange uint32
		if len(g.cfg.TimeRanges) == 7 {
			//by weekley
			var weekday = (now.Local().Weekday() + 1) % 7
			timeRange = g.cfg.TimeRanges[weekday]
		} else {
			timeRange = g.cfg.TimeRanges[0]
		}
		var hour = 1 << now.Local().Hour()
		if timeRange&uint32(hour) == 0 {
			err = errors.New(errors.ERROR_FORBIDDEN, "当前时段不在可访问时段")
			goto responseError
		}
	}

	//进行qps判断
	if g.cfg.QpsMax > 0 {
		if !g.QpsManager.Request(path, g.cfg.QpsMax) {
			if g.cfg.EmulateWhenQpsOver {
				ctx.SetHeader("Bjf-Overflow-Controll", "response-by-emulation")
				g.cfg.Source = -1
				g.cfg.Gateway = -1
			} else {
				err = errors.New(errors.ERROR_FORBIDDEN, "服务已超出访问限制")
				goto responseError
			}
		}
	}

	//进行转发操作
	{
		var openApiCallConfg openapi.CallConfig
		var method = ctx.ReqMethod()
		var hasChange bool
		openApiCallConfg.NameOfMethod = method
		openApiCallConfg.NameOfPath = path
		openApiCallConfg.Input = make(map[string]interface{})
		openApiCallConfg.ExtraAuthInfos = nil
		openApiCallConfg.RequestType = ctx.ReqHeader().Get("Content-Type")

		if strings.Contains(openApiCallConfg.RequestType, ";") {
			openApiCallConfg.RequestType = strings.Split(openApiCallConfg.RequestType, ";")[0]
		}

		switch openApiCallConfg.RequestType {
		case "application/x-www-form-urlencoded", "multipart/form-data":
			openApiCallConfg.RequestBody = ctx.PostForm()
		default:
			openApiCallConfg.RequestBody, _ = ioutil.ReadAll(ctx.Body())
		}

		//TODO 应具备负载均衡能力
		server := g.Targets.SelectOneServer()

		fmt.Println("server:", server)

		op, nP, nM, er := openapi.GetMethod(g.Def, openApiCallConfg.NameOfPath, openApiCallConfg.NameOfMethod)

		if errors.Code(er) == errors.ERR_NO_SUCH_METHOD && g.ApiCfg.Append {
			logrus.Infof("追加模式，自动添加方法[%s]%s,请注意，追加模式下，不会自动添加参数等详细信息，如需添加，请手动修改openapi文件", method, path)
			op, nM = openapi.AddMethod(g.Def, path, method)
			er = nil
			hasChange = true
		}

		// path_def, ok := def.Paths[path]
		if er == nil {
			// op := path_def.GetOperation(method)
			if op != nil {
				//处理query参数
				var values = ctx.ReqURL().Query()
				var handledMark = make(map[string]bool)
				for k, _ := range values {
					handledMark[k] = false
				}

				//入参判断
				for _, p := range op.Parameters {
					switch p.Value.In {
					case "query":
						//这个地方使用FormValue会影响Gw状态下的body获取，暂时先使用QueryString，后期再考虑如何切分
						var value = values.Get(p.Value.Name)
						if value != "" {
							handledMark[p.Value.Name] = true
						}
						if p.Value.Required && value == "" {
							err = fmt.Errorf("参数%s要求为在%s必须传入但是检测为空值", p.Value.Name, p.Value.In)
							goto responseError
						} else if p.Value.Schema != nil {
							if hasChangeSingle, value, err := g.transformer.CheckParameter(p.Value.Name, p.Value.Schema.Value, value); err != nil {
								goto responseError
							} else {
								hasChange = hasChange || hasChangeSingle
								if margs, ok := openApiCallConfg.Input.(map[string]interface{}); ok {
									margs[p.Value.Name] = value
								}
							}
						}
					}
				}
				for k, v := range handledMark {
					if !v && len(values[k]) > 0 && g.ApiCfg.Append {
						var schema = &openapi3.Schema{
							Type: &openapi3.Types{"string"},
						}
						op.Parameters = append(op.Parameters, &openapi3.ParameterRef{
							Value: &openapi3.Parameter{
								Name: k,
								In:   "query",
								Schema: &openapi3.SchemaRef{
									Value: schema,
								},
							},
						})
						if _, value, err := g.transformer.CheckParameter(k, schema, values[k][0]); err != nil {
							goto responseError
						} else if margs, ok := openApiCallConfg.Input.(map[string]interface{}); ok {
							margs[k] = value
						}
						hasChange = true
					}
				}

				// for source
				// 新模式修改：从数据源获得数据
				// if cfg.Source > -1 {
				logrus.Info("gateway to source")
				//TODO 暂不支持服务器参数

				var er error
				var hasChangeInReq bool
				session, err := openapi.NewCalling().SetPathAndMethod(nP, nM, op).SetServer(server).Do(&openApiCallConfg, g.transformer)
				// hasChangeInReq, body, infos, mSel, code, er := openapi.CallByMethodAndReturnBody(op, nP, nM, server, nil, &openApiCallConfg, g.transformer)
				var infos = session.GetResult()
				if er != nil {
					logrus.Errorln("调用数据出错", g.Def, path, method, infos, session.GetCode(), er)
					err = er
				} else if header := infos.Header; header != nil {
					for k, vs := range header {
						for _, v := range vs {
							ctx.SetHeader(k, v)
						}
					}
				}
				hasChange = session.HasChange() || hasChangeInReq
				if g.cfg.SmartEncrypt {
					//智能脱敏功能
					var hasEncyptUpdate bool
					hasEncyptUpdate, err = session.TestResponse(g.transformer)
					if err == nil {
						logrus.WithField("return", infos).Info("source gateway and encrypt success")
						parsed := infos.Parsed
						if parsed != nil {
							ctx.UseRender(infos.Type)
							ctx.RespondWithStatus(parsed, session.GetCode())
						} else {
							err = errors.New(errors.ERR_VALIDATE_SCHEMA_ERROR, "数据格式错误")
							goto responseError
						}
					} else {
						logrus.Errorln("smart encrypt error", err)
					}
					hasChange = hasChange || hasEncyptUpdate
				} else {
					ctx.UseRender("raw")
					ctx.RespondWithStatus(session.GetResponse().Body, session.GetCode())
					logrus.Info("response success")
				}
				if hasChange {
					logrus.Infoln("OpenAPI定义已更新，正在保存")
					go config.SaveYaml(g.Def, g.ApiCfg.Api)
				}
				return err
				// }

			}
		} else {
			err = errors.New(errors.ERR_NO_SUCH_METHOD, "调用路径不存在")
		}

	}
responseError:
	logrus.Errorln("网关返回错误", err)
	ctx.AllowRender("json")
	ctx.UseRender("json")
	return err
}
